﻿using ClusteringProblem.Maths.Metrics;
using ClusteringProblem.Model;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClusteringProblem.Algorithms.DensityMethod
{
	public class DBSCAN : IClusteringAlgorithm
	{		
		#region Pola

		// promień - otoczenie obiektu
		private double epsilon;

		// minimalna liczba punktów w otoczeniu obiektu
		private int minPoints;

		// dane do grupowania
		private List<IAntType> data;

        private IDistance distance;

		#endregion

		#region Konstruktor

		/// <summary>
		/// Konstruktor klasy DBSCAN
		/// </summary>
		/// <param name="epsilon"></param>
		/// <param name="minPoints"></param>
		public DBSCAN(double epsilon, int minPoints, IDistance distance)
		{            
			this.epsilon = epsilon;
			this.minPoints = minPoints;
            this.distance = distance;         
		}

		#endregion        

		#region Właściwości

		public List<Tuple<int, List<IAntType>>> Result { get; set; }

		#endregion

		#region Metody

		/// <summary>
		/// Uruchamia proces grupowania danych
		/// </summary>
		/// <param name="data">dane do grupowania</param>
		public void RunAlgorithm(List<IAntType> data)
		{
			this.data = data;
			int groupIndex = 1;
            List<Tuple<int, List<IAntType>>> result = new List<Tuple<int, List<IAntType>>>();

            // oznaczenie wszystkich obiektów jako nieodwiedzonych w zbiorze
            foreach (var item in this.data)
            {
                item.ResultGroupID = 0.ToString();
            }

            foreach (var item in this.data)
            {
                if (Convert.ToInt32(item.ResultGroupID) == 0)
                {
                    if (ExpandCluster(item, groupIndex))
                        groupIndex++;
                }
            }

			string maxGroupID = this.data.OrderBy(a => a.ResultGroupID).Last().ResultGroupID;

            for (int i = 1; i < Convert.ToInt32(maxGroupID) + 1; i++)
            {
                result.Add(new Tuple<int,List<IAntType>>(i, new List<IAntType>()));
            }

            foreach (var item in this.data)
            {
                if (Convert.ToInt32(item.ResultGroupID) > 0)
                {
                    result[Convert.ToInt32(item.ResultGroupID) - 1].Item2.Add(item);
                }
            }
			

			Result = result;
		}

		/// <summary>
		/// Rozwinięcie grupy
		/// </summary>
		/// <param name="point"></param>
		/// <param name="N"></param>
		/// <param name="C"></param>
		private bool ExpandCluster(IAntType ant, int groupID)
		{
            List<IAntType> neighboursList = GetNeighbours(ant, epsilon);

            // jeśli brak rdzeni 
            if (neighboursList.Count < minPoints)
            {
                // oznaczenie obiektu jako NOISE
                ant.ResultGroupID = (-1).ToString();
                return false;
            }
            else
            {
                for (int i = 0; i < neighboursList.Count; i++)
                {
                    neighboursList[i].ResultGroupID = groupID.ToString();
                }

                neighboursList.Remove(ant);

                while (neighboursList.Count > 0)
                {
                    IAntType currentAnt = neighboursList[0];
                    List<IAntType> result = GetNeighbours(currentAnt, epsilon);
                    if (result.Count >= minPoints)
                    {
                        for (int i = 0; i < result.Count; i++)
                        {
                            IAntType resultAnt = result[i];
                            if (Convert.ToInt32(resultAnt.ResultGroupID) == 0 ||
                                Convert.ToInt32(resultAnt.ResultGroupID) == -1)
                            {
                                if (Convert.ToInt32(resultAnt.ResultGroupID) == 0 )
                                {
                                    neighboursList.Add(resultAnt);
                                }
                                resultAnt.ResultGroupID = groupID.ToString();
                            }
                        }
                    }

                    neighboursList.Remove(currentAnt);
                }
                return true;
            }            
		}        

		/// <summary>
		/// Pobranie listy sąsiedzctwa
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
        private List<IAntType> GetNeighbours(IAntType ant, double epsilon)
        {
            List<IAntType> neighboursList = new List<IAntType>();
            foreach (var item in this.data)
            {
                double _distance = distance.ComputeDistance(ant, item);
                if (_distance <= epsilon)
                {
                    neighboursList.Add(item);
                }
            }

            return neighboursList;
        }

        //private double ComputeDistance(IAntType ant, IAntType item)
        //{
        //    double distance = 0.0;
        //    for (int i = 0; i < ant.Vector.Length; i++)
        //    {
        //        distance += Math.Pow(ant.Vector[i] - item.Vector[i], 2.0);
        //    }

        //    return Math.Sqrt(distance);
        //}

		

				
		#endregion
	}
}


		